|
1
|
|
|
/*! LAB.js (LABjs :: Loading And Blocking JavaScript) |
|
2
|
|
|
v2.0.3 (c) Kyle Simpson |
|
3
|
|
|
MIT License |
|
4
|
|
|
*/ |
|
5
|
|
|
|
|
6
|
|
|
(function(global){ |
|
7
|
|
|
var _$LAB = global.$LAB, |
|
8
|
|
|
|
|
9
|
|
|
// constants for the valid keys of the options object |
|
10
|
|
|
_UseLocalXHR = "UseLocalXHR", |
|
11
|
|
|
_AlwaysPreserveOrder = "AlwaysPreserveOrder", |
|
12
|
|
|
_AllowDuplicates = "AllowDuplicates", |
|
13
|
|
|
_CacheBust = "CacheBust", |
|
14
|
|
|
/*!START_DEBUG*/_Debug = "Debug",/*!END_DEBUG*/ |
|
15
|
|
|
_BasePath = "BasePath", |
|
16
|
|
|
|
|
17
|
|
|
// stateless variables used across all $LAB instances |
|
18
|
|
|
root_page = /^[^?#]*\//.exec(location.href)[0], |
|
19
|
|
|
root_domain = /^\w+\:\/\/\/?[^\/]+/.exec(root_page)[0], |
|
20
|
|
|
append_to = document.head || document.getElementsByTagName("head"), |
|
21
|
|
|
|
|
22
|
|
|
// inferences... ick, but still necessary |
|
23
|
|
|
opera_or_gecko = (global.opera && Object.prototype.toString.call(global.opera) == "[object Opera]") || ("MozAppearance" in document.documentElement.style), |
|
24
|
|
|
|
|
25
|
|
|
/*!START_DEBUG*/ |
|
26
|
|
|
// console.log() and console.error() wrappers |
|
27
|
|
|
log_msg = function(){}, |
|
28
|
|
|
log_error = log_msg, |
|
29
|
|
|
/*!END_DEBUG*/ |
|
30
|
|
|
|
|
31
|
|
|
// feature sniffs (yay!) |
|
32
|
|
|
test_script_elem = document.createElement("script"), |
|
33
|
|
|
explicit_preloading = typeof test_script_elem.preload == "boolean", // http://wiki.whatwg.org/wiki/Script_Execution_Control#Proposal_1_.28Nicholas_Zakas.29 |
|
34
|
|
|
real_preloading = explicit_preloading || (test_script_elem.readyState && test_script_elem.readyState == "uninitialized"), // will a script preload with `src` set before DOM append? |
|
35
|
|
|
script_ordered_async = !real_preloading && test_script_elem.async === true, // http://wiki.whatwg.org/wiki/Dynamic_Script_Execution_Order |
|
36
|
|
|
|
|
37
|
|
|
// XHR preloading (same-domain) and cache-preloading (remote-domain) are the fallbacks (for some browsers) |
|
38
|
|
|
xhr_or_cache_preloading = !real_preloading && !script_ordered_async && !opera_or_gecko |
|
39
|
|
|
; |
|
40
|
|
|
|
|
41
|
|
|
/*!START_DEBUG*/ |
|
42
|
|
|
// define console wrapper functions if applicable |
|
43
|
|
|
if (global.console && global.console.log) { |
|
44
|
|
|
if (!global.console.error) global.console.error = global.console.log; |
|
45
|
|
|
log_msg = function(msg) { global.console.log(msg); }; |
|
46
|
|
|
log_error = function(msg,err) { global.console.error(msg,err); }; |
|
47
|
|
|
} |
|
48
|
|
|
/*!END_DEBUG*/ |
|
49
|
|
|
|
|
50
|
|
|
// test for function |
|
51
|
|
|
function is_func(func) { return Object.prototype.toString.call(func) == "[object Function]"; } |
|
52
|
|
|
|
|
53
|
|
|
// test for array |
|
54
|
|
|
function is_array(arr) { return Object.prototype.toString.call(arr) == "[object Array]"; } |
|
55
|
|
|
|
|
56
|
|
|
// make script URL absolute/canonical |
|
57
|
|
|
function canonical_uri(src,base_path) { |
|
58
|
|
|
var absolute_regex = /^\w+\:\/\//; |
|
59
|
|
|
|
|
60
|
|
|
// is `src` is protocol-relative (begins with // or ///), prepend protocol |
|
61
|
|
|
if (/^\/\/\/?/.test(src)) { |
|
62
|
|
|
src = location.protocol + src; |
|
63
|
|
|
} |
|
64
|
|
|
// is `src` page-relative? (not an absolute URL, and not a domain-relative path, beginning with /) |
|
65
|
|
|
else if (!absolute_regex.test(src) && src.charAt(0) != "/") { |
|
66
|
|
|
// prepend `base_path`, if any |
|
67
|
|
|
src = (base_path || "") + src; |
|
68
|
|
|
} |
|
69
|
|
|
// make sure to return `src` as absolute |
|
70
|
|
|
return absolute_regex.test(src) ? src : ((src.charAt(0) == "/" ? root_domain : root_page) + src); |
|
71
|
|
|
} |
|
72
|
|
|
|
|
73
|
|
|
// merge `source` into `target` |
|
74
|
|
|
function merge_objs(source,target) { |
|
75
|
|
|
for (var k in source) { if (source.hasOwnProperty(k)) { |
|
76
|
|
|
target[k] = source[k]; // TODO: does this need to be recursive for our purposes? |
|
77
|
|
|
}} |
|
78
|
|
|
return target; |
|
79
|
|
|
} |
|
80
|
|
|
|
|
81
|
|
|
// does the chain group have any ready-to-execute scripts? |
|
82
|
|
|
function check_chain_group_scripts_ready(chain_group) { |
|
83
|
|
|
var any_scripts_ready = false; |
|
84
|
|
|
for (var i=0; i<chain_group.scripts.length; i++) { |
|
85
|
|
|
if (chain_group.scripts[i].ready && chain_group.scripts[i].exec_trigger) { |
|
86
|
|
|
any_scripts_ready = true; |
|
87
|
|
|
chain_group.scripts[i].exec_trigger(); |
|
88
|
|
|
chain_group.scripts[i].exec_trigger = null; |
|
89
|
|
|
} |
|
90
|
|
|
} |
|
91
|
|
|
return any_scripts_ready; |
|
92
|
|
|
} |
|
93
|
|
|
|
|
94
|
|
|
// creates a script load listener |
|
95
|
|
|
function create_script_load_listener(elem,registry_item,flag,onload) { |
|
96
|
|
|
elem.onload = elem.onreadystatechange = function() { |
|
97
|
|
|
if ((elem.readyState && elem.readyState != "complete" && elem.readyState != "loaded") || registry_item[flag]) return; |
|
98
|
|
|
elem.onload = elem.onreadystatechange = null; |
|
99
|
|
|
onload(); |
|
100
|
|
|
}; |
|
101
|
|
|
} |
|
102
|
|
|
|
|
103
|
|
|
// script executed handler |
|
104
|
|
|
function script_executed(registry_item) { |
|
105
|
|
|
registry_item.ready = registry_item.finished = true; |
|
106
|
|
|
for (var i=0; i<registry_item.finished_listeners.length; i++) { |
|
107
|
|
|
registry_item.finished_listeners[i](); |
|
108
|
|
|
} |
|
109
|
|
|
registry_item.ready_listeners = []; |
|
110
|
|
|
registry_item.finished_listeners = []; |
|
111
|
|
|
} |
|
112
|
|
|
|
|
113
|
|
|
// make the request for a scriptha |
|
114
|
|
|
function request_script(chain_opts,script_obj,registry_item,onload,preload_this_script) { |
|
115
|
|
|
// setTimeout() "yielding" prevents some weird race/crash conditions in older browsers |
|
116
|
|
|
setTimeout(function(){ |
|
117
|
|
|
var script, src = script_obj.real_src, xhr; |
|
118
|
|
|
|
|
119
|
|
|
// don't proceed until `append_to` is ready to append to |
|
120
|
|
|
if ("item" in append_to) { // check if `append_to` ref is still a live node list |
|
121
|
|
|
if (!append_to[0]) { // `append_to` node not yet ready |
|
122
|
|
|
// try again in a little bit -- note: will re-call the anonymous function in the outer setTimeout, not the parent `request_script()` |
|
123
|
|
|
setTimeout(arguments.callee,25); |
|
124
|
|
|
return; |
|
125
|
|
|
} |
|
126
|
|
|
// reassign from live node list ref to pure node ref -- avoids nasty IE bug where changes to DOM invalidate live node lists |
|
127
|
|
|
append_to = append_to[0]; |
|
128
|
|
|
} |
|
129
|
|
|
script = document.createElement("script"); |
|
130
|
|
|
if (script_obj.type) script.type = script_obj.type; |
|
131
|
|
|
if (script_obj.charset) script.charset = script_obj.charset; |
|
132
|
|
|
|
|
133
|
|
|
// should preloading be used for this script? |
|
134
|
|
|
if (preload_this_script) { |
|
135
|
|
|
// real script preloading? |
|
136
|
|
|
if (real_preloading) { |
|
137
|
|
|
/*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("start script preload: "+src);/*!END_DEBUG*/ |
|
|
|
|
|
|
138
|
|
|
registry_item.elem = script; |
|
139
|
|
|
if (explicit_preloading) { // explicit preloading (aka, Zakas' proposal) |
|
140
|
|
|
script.preload = true; |
|
141
|
|
|
script.onpreload = onload; |
|
142
|
|
|
} |
|
143
|
|
|
else { |
|
144
|
|
|
script.onreadystatechange = function(){ |
|
145
|
|
|
if (script.readyState == "loaded") onload(); |
|
146
|
|
|
}; |
|
147
|
|
|
} |
|
148
|
|
|
script.src = src; |
|
149
|
|
|
// NOTE: no append to DOM yet, appending will happen when ready to execute |
|
150
|
|
|
} |
|
151
|
|
|
// same-domain and XHR allowed? use XHR preloading |
|
152
|
|
|
else if (preload_this_script && src.indexOf(root_domain) == 0 && chain_opts[_UseLocalXHR]) { |
|
153
|
|
|
xhr = new XMLHttpRequest(); // note: IE never uses XHR (it supports true preloading), so no more need for ActiveXObject fallback for IE <= 7 |
|
154
|
|
|
/*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("start script preload (xhr): "+src);/*!END_DEBUG*/ |
|
155
|
|
|
xhr.onreadystatechange = function() { |
|
156
|
|
|
if (xhr.readyState == 4) { |
|
157
|
|
|
xhr.onreadystatechange = function(){}; // fix a memory leak in IE |
|
158
|
|
|
registry_item.text = xhr.responseText + "\n//@ sourceURL=" + src; // http://blog.getfirebug.com/2009/08/11/give-your-eval-a-name-with-sourceurl/ |
|
159
|
|
|
onload(); |
|
160
|
|
|
} |
|
161
|
|
|
}; |
|
162
|
|
|
xhr.open("GET",src); |
|
163
|
|
|
xhr.send(); |
|
164
|
|
|
} |
|
165
|
|
|
// as a last resort, use cache-preloading |
|
166
|
|
|
else { |
|
167
|
|
|
/*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("start script preload (cache): "+src);/*!END_DEBUG*/ |
|
168
|
|
|
script.type = "text/cache-script"; |
|
169
|
|
|
create_script_load_listener(script,registry_item,"ready",function() { |
|
170
|
|
|
append_to.removeChild(script); |
|
171
|
|
|
onload(); |
|
172
|
|
|
}); |
|
173
|
|
|
script.src = src; |
|
174
|
|
|
append_to.insertBefore(script,append_to.firstChild); |
|
175
|
|
|
} |
|
176
|
|
|
} |
|
177
|
|
|
// use async=false for ordered async? parallel-load-serial-execute http://wiki.whatwg.org/wiki/Dynamic_Script_Execution_Order |
|
178
|
|
|
else if (script_ordered_async) { |
|
179
|
|
|
/*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("start script load (ordered async): "+src);/*!END_DEBUG*/ |
|
180
|
|
|
script.async = false; |
|
181
|
|
|
create_script_load_listener(script,registry_item,"finished",onload); |
|
182
|
|
|
script.src = src; |
|
183
|
|
|
append_to.insertBefore(script,append_to.firstChild); |
|
184
|
|
|
} |
|
185
|
|
|
// otherwise, just a normal script element |
|
186
|
|
|
else { |
|
187
|
|
|
/*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("start script load: "+src);/*!END_DEBUG*/ |
|
188
|
|
|
create_script_load_listener(script,registry_item,"finished",onload); |
|
189
|
|
|
script.src = src; |
|
190
|
|
|
append_to.insertBefore(script,append_to.firstChild); |
|
191
|
|
|
} |
|
192
|
|
|
},0); |
|
193
|
|
|
} |
|
194
|
|
|
|
|
195
|
|
|
// create a clean instance of $LAB |
|
196
|
|
|
function create_sandbox() { |
|
197
|
|
|
var global_defaults = {}, |
|
198
|
|
|
can_use_preloading = real_preloading || xhr_or_cache_preloading, |
|
199
|
|
|
queue = [], |
|
200
|
|
|
registry = {}, |
|
201
|
|
|
instanceAPI |
|
202
|
|
|
; |
|
203
|
|
|
|
|
204
|
|
|
// global defaults |
|
205
|
|
|
global_defaults[_UseLocalXHR] = true; |
|
206
|
|
|
global_defaults[_AlwaysPreserveOrder] = false; |
|
207
|
|
|
global_defaults[_AllowDuplicates] = false; |
|
208
|
|
|
global_defaults[_CacheBust] = false; |
|
209
|
|
|
/*!START_DEBUG*/global_defaults[_Debug] = false;/*!END_DEBUG*/ |
|
210
|
|
|
global_defaults[_BasePath] = ""; |
|
211
|
|
|
|
|
212
|
|
|
// execute a script that has been preloaded already |
|
213
|
|
|
function execute_preloaded_script(chain_opts,script_obj,registry_item) { |
|
214
|
|
|
var script; |
|
215
|
|
|
|
|
216
|
|
|
function preload_execute_finished() { |
|
217
|
|
|
if (script != null) { // make sure this only ever fires once |
|
218
|
|
|
script = null; |
|
219
|
|
|
script_executed(registry_item); |
|
220
|
|
|
} |
|
221
|
|
|
} |
|
222
|
|
|
|
|
223
|
|
|
if (registry[script_obj.src].finished) return; |
|
224
|
|
|
if (!chain_opts[_AllowDuplicates]) registry[script_obj.src].finished = true; |
|
225
|
|
|
|
|
226
|
|
|
script = registry_item.elem || document.createElement("script"); |
|
227
|
|
|
if (script_obj.type) script.type = script_obj.type; |
|
228
|
|
|
if (script_obj.charset) script.charset = script_obj.charset; |
|
229
|
|
|
create_script_load_listener(script,registry_item,"finished",preload_execute_finished); |
|
230
|
|
|
|
|
231
|
|
|
// script elem was real-preloaded |
|
232
|
|
|
if (registry_item.elem) { |
|
233
|
|
|
registry_item.elem = null; |
|
234
|
|
|
} |
|
235
|
|
|
// script was XHR preloaded |
|
236
|
|
|
else if (registry_item.text) { |
|
237
|
|
|
script.onload = script.onreadystatechange = null; // script injection doesn't fire these events |
|
238
|
|
|
script.text = registry_item.text; |
|
239
|
|
|
} |
|
240
|
|
|
// script was cache-preloaded |
|
241
|
|
|
else { |
|
242
|
|
|
script.src = script_obj.real_src; |
|
243
|
|
|
} |
|
244
|
|
|
append_to.insertBefore(script,append_to.firstChild); |
|
245
|
|
|
|
|
246
|
|
|
// manually fire execution callback for injected scripts, since events don't fire |
|
247
|
|
|
if (registry_item.text) { |
|
248
|
|
|
preload_execute_finished(); |
|
249
|
|
|
} |
|
250
|
|
|
} |
|
251
|
|
|
|
|
252
|
|
|
// process the script request setup |
|
253
|
|
|
function do_script(chain_opts,script_obj,chain_group,preload_this_script) { |
|
254
|
|
|
var registry_item, |
|
255
|
|
|
registry_items, |
|
256
|
|
|
ready_cb = function(){ script_obj.ready_cb(script_obj,function(){ execute_preloaded_script(chain_opts,script_obj,registry_item); }); }, |
|
257
|
|
|
finished_cb = function(){ script_obj.finished_cb(script_obj,chain_group); } |
|
258
|
|
|
; |
|
259
|
|
|
|
|
260
|
|
|
script_obj.src = canonical_uri(script_obj.src,chain_opts[_BasePath]); |
|
261
|
|
|
script_obj.real_src = script_obj.src + |
|
262
|
|
|
// append cache-bust param to URL? |
|
263
|
|
|
(chain_opts[_CacheBust] ? ((/\?.*$/.test(script_obj.src) ? "&_" : "?_") + ~~(Math.random()*1E9) + "=") : "") |
|
264
|
|
|
; |
|
265
|
|
|
|
|
266
|
|
|
if (!registry[script_obj.src]) registry[script_obj.src] = {items:[],finished:false}; |
|
267
|
|
|
registry_items = registry[script_obj.src].items; |
|
268
|
|
|
|
|
269
|
|
|
// allowing duplicates, or is this the first recorded load of this script? |
|
270
|
|
|
if (chain_opts[_AllowDuplicates] || registry_items.length == 0) { |
|
271
|
|
|
registry_item = registry_items[registry_items.length] = { |
|
272
|
|
|
ready:false, |
|
273
|
|
|
finished:false, |
|
274
|
|
|
ready_listeners:[ready_cb], |
|
275
|
|
|
finished_listeners:[finished_cb] |
|
276
|
|
|
}; |
|
277
|
|
|
|
|
278
|
|
|
request_script(chain_opts,script_obj,registry_item, |
|
279
|
|
|
// which callback type to pass? |
|
280
|
|
|
( |
|
281
|
|
|
(preload_this_script) ? // depends on script-preloading |
|
282
|
|
|
function(){ |
|
283
|
|
|
registry_item.ready = true; |
|
284
|
|
|
for (var i=0; i<registry_item.ready_listeners.length; i++) { |
|
285
|
|
|
registry_item.ready_listeners[i](); |
|
286
|
|
|
} |
|
287
|
|
|
registry_item.ready_listeners = []; |
|
288
|
|
|
} : |
|
289
|
|
|
function(){ script_executed(registry_item); } |
|
290
|
|
|
), |
|
291
|
|
|
// signal if script-preloading should be used or not |
|
292
|
|
|
preload_this_script |
|
293
|
|
|
); |
|
294
|
|
|
} |
|
295
|
|
|
else { |
|
296
|
|
|
registry_item = registry_items[0]; |
|
297
|
|
|
if (registry_item.finished) { |
|
298
|
|
|
finished_cb(); |
|
299
|
|
|
} |
|
300
|
|
|
else { |
|
301
|
|
|
registry_item.finished_listeners.push(finished_cb); |
|
302
|
|
|
} |
|
303
|
|
|
} |
|
304
|
|
|
} |
|
305
|
|
|
|
|
306
|
|
|
// creates a closure for each separate chain spawned from this $LAB instance, to keep state cleanly separated between chains |
|
307
|
|
|
function create_chain() { |
|
308
|
|
|
var chainedAPI, |
|
309
|
|
|
chain_opts = merge_objs(global_defaults,{}), |
|
310
|
|
|
chain = [], |
|
311
|
|
|
exec_cursor = 0, |
|
312
|
|
|
scripts_currently_loading = false, |
|
313
|
|
|
group |
|
314
|
|
|
; |
|
315
|
|
|
|
|
316
|
|
|
// called when a script has finished preloading |
|
317
|
|
|
function chain_script_ready(script_obj,exec_trigger) { |
|
318
|
|
|
/*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("script preload finished: "+script_obj.real_src);/*!END_DEBUG*/ |
|
|
|
|
|
|
319
|
|
|
script_obj.ready = true; |
|
320
|
|
|
script_obj.exec_trigger = exec_trigger; |
|
321
|
|
|
advance_exec_cursor(); // will only check for 'ready' scripts to be executed |
|
322
|
|
|
} |
|
323
|
|
|
|
|
324
|
|
|
// called when a script has finished executing |
|
325
|
|
|
function chain_script_executed(script_obj,chain_group) { |
|
326
|
|
|
/*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("script execution finished: "+script_obj.real_src);/*!END_DEBUG*/ |
|
|
|
|
|
|
327
|
|
|
script_obj.ready = script_obj.finished = true; |
|
328
|
|
|
script_obj.exec_trigger = null; |
|
329
|
|
|
// check if chain group is all finished |
|
330
|
|
|
for (var i=0; i<chain_group.scripts.length; i++) { |
|
331
|
|
|
if (!chain_group.scripts[i].finished) return; |
|
332
|
|
|
} |
|
333
|
|
|
// chain_group is all finished if we get this far |
|
334
|
|
|
chain_group.finished = true; |
|
335
|
|
|
advance_exec_cursor(); |
|
336
|
|
|
} |
|
337
|
|
|
|
|
338
|
|
|
// main driver for executing each part of the chain |
|
339
|
|
|
function advance_exec_cursor() { |
|
340
|
|
|
while (exec_cursor < chain.length) { |
|
|
|
|
|
|
341
|
|
|
if (is_func(chain[exec_cursor])) { |
|
342
|
|
|
/*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("$LAB.wait() executing: "+chain[exec_cursor]);/*!END_DEBUG*/ |
|
|
|
|
|
|
343
|
|
|
try { chain[exec_cursor++](); } catch (err) { |
|
344
|
|
|
/*!START_DEBUG*/if (chain_opts[_Debug]) log_error("$LAB.wait() error caught: ",err);/*!END_DEBUG*/ |
|
345
|
|
|
} |
|
346
|
|
|
continue; |
|
347
|
|
|
} |
|
348
|
|
|
else if (!chain[exec_cursor].finished) { |
|
349
|
|
|
if (check_chain_group_scripts_ready(chain[exec_cursor])) continue; |
|
350
|
|
|
break; |
|
351
|
|
|
} |
|
352
|
|
|
exec_cursor++; |
|
353
|
|
|
} |
|
354
|
|
|
// we've reached the end of the chain (so far) |
|
355
|
|
|
if (exec_cursor == chain.length) { |
|
356
|
|
|
scripts_currently_loading = false; |
|
357
|
|
|
group = false; |
|
358
|
|
|
} |
|
359
|
|
|
} |
|
360
|
|
|
|
|
361
|
|
|
// setup next chain script group |
|
362
|
|
|
function init_script_chain_group() { |
|
363
|
|
|
if (!group || !group.scripts) { |
|
364
|
|
|
chain.push(group = {scripts:[],finished:true}); |
|
365
|
|
|
} |
|
366
|
|
|
} |
|
367
|
|
|
|
|
368
|
|
|
// API for $LAB chains |
|
369
|
|
|
chainedAPI = { |
|
370
|
|
|
// start loading one or more scripts |
|
371
|
|
|
script:function(){ |
|
372
|
|
|
for (var i=0; i<arguments.length; i++) { |
|
373
|
|
|
(function(script_obj,script_list){ |
|
374
|
|
|
var splice_args; |
|
375
|
|
|
|
|
376
|
|
|
if (!is_array(script_obj)) { |
|
377
|
|
|
script_list = [script_obj]; |
|
378
|
|
|
} |
|
379
|
|
|
for (var j=0; j<script_list.length; j++) { |
|
380
|
|
|
init_script_chain_group(); |
|
381
|
|
|
script_obj = script_list[j]; |
|
382
|
|
|
|
|
383
|
|
|
if (is_func(script_obj)) script_obj = script_obj(); |
|
384
|
|
|
if (!script_obj) continue; |
|
385
|
|
|
if (is_array(script_obj)) { |
|
386
|
|
|
// set up an array of arguments to pass to splice() |
|
387
|
|
|
splice_args = [].slice.call(script_obj); // first include the actual array elements we want to splice in |
|
388
|
|
|
splice_args.unshift(j,1); // next, put the `index` and `howMany` parameters onto the beginning of the splice-arguments array |
|
389
|
|
|
[].splice.apply(script_list,splice_args); // use the splice-arguments array as arguments for splice() |
|
390
|
|
|
j--; // adjust `j` to account for the loop's subsequent `j++`, so that the next loop iteration uses the same `j` index value |
|
|
|
|
|
|
391
|
|
|
continue; |
|
392
|
|
|
} |
|
393
|
|
|
if (typeof script_obj == "string") script_obj = {src:script_obj}; |
|
394
|
|
|
script_obj = merge_objs(script_obj,{ |
|
395
|
|
|
ready:false, |
|
396
|
|
|
ready_cb:chain_script_ready, |
|
397
|
|
|
finished:false, |
|
398
|
|
|
finished_cb:chain_script_executed |
|
399
|
|
|
}); |
|
400
|
|
|
group.finished = false; |
|
401
|
|
|
group.scripts.push(script_obj); |
|
402
|
|
|
|
|
403
|
|
|
do_script(chain_opts,script_obj,group,(can_use_preloading && scripts_currently_loading)); |
|
|
|
|
|
|
404
|
|
|
scripts_currently_loading = true; |
|
405
|
|
|
|
|
406
|
|
|
if (chain_opts[_AlwaysPreserveOrder]) chainedAPI.wait(); |
|
407
|
|
|
} |
|
408
|
|
|
})(arguments[i],arguments[i]); |
|
409
|
|
|
} |
|
410
|
|
|
return chainedAPI; |
|
411
|
|
|
}, |
|
412
|
|
|
// force LABjs to pause in execution at this point in the chain, until the execution thus far finishes, before proceeding |
|
413
|
|
|
wait:function(){ |
|
414
|
|
|
if (arguments.length > 0) { |
|
415
|
|
|
for (var i=0; i<arguments.length; i++) { |
|
416
|
|
|
chain.push(arguments[i]); |
|
417
|
|
|
} |
|
418
|
|
|
group = chain[chain.length-1]; |
|
419
|
|
|
} |
|
420
|
|
|
else group = false; |
|
421
|
|
|
|
|
422
|
|
|
advance_exec_cursor(); |
|
423
|
|
|
|
|
424
|
|
|
return chainedAPI; |
|
425
|
|
|
} |
|
426
|
|
|
}; |
|
427
|
|
|
|
|
428
|
|
|
// the first chain link API (includes `setOptions` only this first time) |
|
429
|
|
|
return { |
|
430
|
|
|
script:chainedAPI.script, |
|
431
|
|
|
wait:chainedAPI.wait, |
|
432
|
|
|
setOptions:function(opts){ |
|
433
|
|
|
merge_objs(opts,chain_opts); |
|
434
|
|
|
return chainedAPI; |
|
435
|
|
|
} |
|
436
|
|
|
}; |
|
437
|
|
|
} |
|
438
|
|
|
|
|
439
|
|
|
// API for each initial $LAB instance (before chaining starts) |
|
440
|
|
|
instanceAPI = { |
|
441
|
|
|
// main API functions |
|
442
|
|
|
setGlobalDefaults:function(opts){ |
|
443
|
|
|
merge_objs(opts,global_defaults); |
|
444
|
|
|
return instanceAPI; |
|
445
|
|
|
}, |
|
446
|
|
|
setOptions:function(){ |
|
447
|
|
|
return create_chain().setOptions.apply(null,arguments); |
|
448
|
|
|
}, |
|
449
|
|
|
script:function(){ |
|
450
|
|
|
return create_chain().script.apply(null,arguments); |
|
451
|
|
|
}, |
|
452
|
|
|
wait:function(){ |
|
453
|
|
|
return create_chain().wait.apply(null,arguments); |
|
454
|
|
|
}, |
|
455
|
|
|
|
|
456
|
|
|
// built-in queuing for $LAB `script()` and `wait()` calls |
|
457
|
|
|
// useful for building up a chain programmatically across various script locations, and simulating |
|
458
|
|
|
// execution of the chain |
|
459
|
|
|
queueScript:function(){ |
|
460
|
|
|
queue[queue.length] = {type:"script", args:[].slice.call(arguments)}; |
|
461
|
|
|
return instanceAPI; |
|
462
|
|
|
}, |
|
463
|
|
|
queueWait:function(){ |
|
464
|
|
|
queue[queue.length] = {type:"wait", args:[].slice.call(arguments)}; |
|
465
|
|
|
return instanceAPI; |
|
466
|
|
|
}, |
|
467
|
|
|
runQueue:function(){ |
|
468
|
|
|
var $L = instanceAPI, len=queue.length, i=len, val; |
|
469
|
|
|
for (;--i>=0;) { |
|
470
|
|
|
val = queue.shift(); |
|
471
|
|
|
$L = $L[val.type].apply(null,val.args); |
|
472
|
|
|
} |
|
473
|
|
|
return $L; |
|
474
|
|
|
}, |
|
475
|
|
|
|
|
476
|
|
|
// rollback `[global].$LAB` to what it was before this file was loaded, the return this current instance of $LAB |
|
477
|
|
|
noConflict:function(){ |
|
478
|
|
|
global.$LAB = _$LAB; |
|
479
|
|
|
return instanceAPI; |
|
480
|
|
|
}, |
|
481
|
|
|
|
|
482
|
|
|
// create another clean instance of $LAB |
|
483
|
|
|
sandbox:function(){ |
|
484
|
|
|
return create_sandbox(); |
|
485
|
|
|
} |
|
486
|
|
|
}; |
|
487
|
|
|
|
|
488
|
|
|
return instanceAPI; |
|
489
|
|
|
} |
|
490
|
|
|
|
|
491
|
|
|
// create the main instance of $LAB |
|
492
|
|
|
global.$LAB = create_sandbox(); |
|
493
|
|
|
|
|
494
|
|
|
|
|
495
|
|
|
/* The following "hack" was suggested by Andrea Giammarchi and adapted from: http://webreflection.blogspot.com/2009/11/195-chars-to-help-lazy-loading.html |
|
496
|
|
|
NOTE: this hack only operates in FF and then only in versions where document.readyState is not present (FF < 3.6?). |
|
497
|
|
|
|
|
498
|
|
|
The hack essentially "patches" the **page** that LABjs is loaded onto so that it has a proper conforming document.readyState, so that if a script which does |
|
499
|
|
|
proper and safe dom-ready detection is loaded onto a page, after dom-ready has passed, it will still be able to detect this state, by inspecting the now hacked |
|
500
|
|
|
document.readyState property. The loaded script in question can then immediately trigger any queued code executions that were waiting for the DOM to be ready. |
|
501
|
|
|
For instance, jQuery 1.4+ has been patched to take advantage of document.readyState, which is enabled by this hack. But 1.3.2 and before are **not** safe or |
|
502
|
|
|
fixed by this hack, and should therefore **not** be lazy-loaded by script loader tools such as LABjs. |
|
503
|
|
|
*/ |
|
504
|
|
|
(function(addEvent,domLoaded,handler){ |
|
505
|
|
|
if (document.readyState == null && document[addEvent]){ |
|
506
|
|
|
document.readyState = "loading"; |
|
507
|
|
|
document[addEvent](domLoaded,handler = function(){ |
|
508
|
|
|
document.removeEventListener(domLoaded,handler,false); |
|
509
|
|
|
document.readyState = "complete"; |
|
510
|
|
|
},false); |
|
511
|
|
|
} |
|
512
|
|
|
})("addEventListener","DOMContentLoaded"); |
|
513
|
|
|
|
|
514
|
|
|
})(this); |